package YAPclient;

import java.awt.BasicStroke;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.JPanel;
import javax.swing.Timer;

import YAPclient.GameInformation.Ball;
import YAPclient.GameInformation.GameMode;
import YAPclient.GameInformation.Side;
import YAPenvironment.YAPconstant;
import YAPenvironment.YAPconstant.Command;
import YAPenvironment.YAPconstant.SideState;
import YAPserver.gameObjects.Line;
import YAPserver.gameObjects.Point;

/**
 * @author Xaro
 * This class paints the whole Playground, where the field is being drawn. The information used is stored in {@link GameInformation}. It also handles the listeners on the Playground (e.g. pickSide, hover color)
 */
public class Playground extends JPanel implements MouseListener,MouseMotionListener,ActionListener,ComponentListener   {
	private static final long serialVersionUID = -1112535692195155665L;
	// Variable for Cursor, used to set the mouse cursor on hover effect properly
	private boolean isUseChangedCursor=false;
	// Variable with the current mouse position
	private Point m_CurrentMousePos;
	// Resized factor
	private double dResized;
	// Variables for timed-rotation
	private long systemMillis;
	private Timer tim;
	private int rotTime;
	private double rotAngle;
	
	/**
	 * Playground constructor
	 * @param gm {@link GameMode}
	 */
	public Playground(GameMode gm){
	    addMouseListener(this);
	    addMouseMotionListener(this);		
	    addComponentListener(this);
	    setDoubleBuffered(true);
		DataModul.gInfo.setGameMode(gm);
		setOpaque(true);
		setVisible(true);
		Thread t = new Thread(){
			public void run(){
				while(true){
					repaint(); 
					try {
						this.sleep((int)(1000/YAPconstant.C_FPS_LIMIT));
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			
		};
		t.start();
	}

	/**
	 * Makes sure the Playground is quadratic
	 * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentResized(ComponentEvent arg0) {
	    Rectangle b = arg0.getComponent().getBounds();
	    int w = b.width;
	    int h = b.height;
	    if (h<w){
	    	w=h;
	    }
	    arg0.getComponent().setBounds(b.x, b.y, w, w);
        repaint();	
    }	
	
	/**
	 * Paints everything 
	 * @see javax.swing.JComponent#paint(java.awt.Graphics)
	 */
	@Override
	public void paint(Graphics g) {
		try{
			GameInformation gi = DataModul.gInfo;
			if (gi != null){
				int t = YAPconstant.C_LINETHICKNESS;
				Graphics2D g2 = (Graphics2D) g;
				// Set Anti Aliasing
				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
				g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
						RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				
				// Set width/heigth of paint-area
				int h = this.getVisibleRect().height;
				int w = this.getVisibleRect().width;
				int size = YAPconstant.C_GAMESIZE+2*t; 
				if (h<w){
					w=h;
				}
				else{
					h=w;
				}
				
				// Translate it!
				g2.translate(t, t);
				// Scale it!
				dResized =(double)h/(double)(size+2*t); 
				g2.scale(dResized, dResized);
				// Set default black background
				g2.setBackground(YAPconstant.C_BACKGROUND_COLOR);
				g2.clearRect(-(int)((size*Math.sqrt(2)-size)/2), -(int)((size*Math.sqrt(2)-size)/2), (int)(size*Math.sqrt(2)), (int)(size*Math.sqrt(2)));

				// Call getDrawXXX ONLY ONCE (or everything is getting mixed up)
				// To hold and use the pointer is save.
				ArrayList<Side> sides = gi.getDrawSides();
				int sizeSides = sides.size();
				ArrayList<Ball> balls = gi.getDrawBalls();
				int sizeBalls = balls.size();
				ArrayList<Line> sliders = gi.getDrawSliders();
				int sizeSliders = sliders.size();
				ArrayList<Ball> mods = gi.getDrawMods();
				int sizeMods = mods.size();
					
				// For displaying the right mouse cursor
				isUseChangedCursor = false;
								
				switch (gi.getGameMode()) {
				case none:
					break;
				case lobbyOverview:
					// Walls
					g2.setColor(YAPconstant.C_WALL_COLOR);
					g2.setStroke(new BasicStroke(t,BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
					if (sizeSides > 2){
						for(int i=0; i<sizeSides; i++){
							Point s1 = sides.get(i).lInner.getStartPoint();
							Point s2 = sides.get(i).lInner.getEndPoint();
							g2.drawLine((int) s1.getXPos(), (int)s1.getYPos(), (int)s2.getXPos(), (int)s2.getYPos());
						}
					}
					break;
				case lobby:
					if (sizeSides > 2){
						for(int i=0; i<sizeSides; i++){
							g2.setColor(sides.get(i).cCurr);
							if (getCurrentMousePos() != null){
								if (sides.get(i).pWall.contains(getCurrentMousePos().getXPos(), getCurrentMousePos().getYPos())){
									if ((sides.get(i).cCurr != YAPconstant.C_ASSIGNED_COLOR) && (sides.get(i).cCurr != YAPconstant.C_OWNASSIGNED_COLOR)){
										g2.setColor(YAPconstant.C_HOVER_COLOR);
										if (!isUseChangedCursor){
											setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
											isUseChangedCursor = true;
										}
									}
								}
							}
							g2.fillPolygon(sides.get(i).pWall);
						}
						if (DataModul.gInfo.getStartCounter() != 0){
							g2.setColor(YAPconstant.C_STARTCOUNTER_COLOR);
							g2.setFont(new Font("dialog", Font.BOLD, 96));
							g2.drawString(DataModul.gInfo.getStartCounter()+"", size/2-48, size/2+24);
						}
					}
					break;
				case game:
					// Rotate it!
					g2.rotate(DataModul.gInfo.getCurrAngle(), size/2, size/2);
					// Pongs
					g2.setColor(YAPconstant.C_PONG_COLOR);
					for (int i = 0; i<sizeBalls;i++){
						g2.fillArc((int)(balls.get(i).p.getXPos()-balls.get(i).r), (int)(balls.get(i).p.getYPos()-balls.get(i).r), (int)(balls.get(i).r*2),(int)(balls.get(i).r*2),0,360);
					}
					// Mod-Walls (2x to make sure the Mod-Wall is drawn below the wall)
					for (int i= 0; i<sizeSides;i++){
						switch(sides.get(i).sState){
						case modWall:
							g2.setColor(YAPconstant.C_MODWALL_COLOR);
							g2.fillPolygon(sides.get(i).pMod);
							break;
						case none:
							break;
						}
					}
					// Walls
					for (int i= 0; i<sizeSides;i++){
						switch(sides.get(i).sState){
						case wall:
//							g2.setStroke(new BasicStroke(t,BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
							g2.setColor(YAPconstant.C_WALL_COLOR);
							g2.fillPolygon(sides.get(i).pWall);
							//g2.drawLine((int)sides.get(i).lInner.getStartPoint().getXPos(), (int)sides.get(i).lInner.getStartPoint().getYPos(), (int)sides.get(i).lInner.getEndPoint().getXPos(), (int)sides.get(i).lInner.getEndPoint().getYPos());
							break;
						case none:
							break;
						}
					}
					// Mods
					for (int i = 0; i<sizeMods;i++){
						g2.setColor(mods.get(i).c);
						g2.fillArc((int)(mods.get(i).p.getXPos()-mods.get(i).r), (int)(mods.get(i).p.getYPos()-mods.get(i).r), (int)(mods.get(i).r*2),(int)(mods.get(i).r*2),0,360);
					}
					// Circles at Edges
					g2.setColor(YAPconstant.C_WALL_COLOR);
					if (sizeSides > 2){
						for(int i=0; i<sizeSides; i++){
							g2.fillPolygon(sides.get(i).pEdge);
//							Point s1 = sides.get(i).lOuter.getStartPoint();
//							if (i==0){
//								if (!((sides.get(sizeSides-1).sState == SideState.wall) && (sides.get(i).sState == SideState.wall))){
//									g2.fillArc((int)(s1.getXPos()-t/2), (int)(s1.getYPos()-t/2),t, t, 0, 360);
//								}
//							}
//							else{
//								if (!((sides.get(i-1).sState == SideState.wall) && (sides.get(i).sState == SideState.wall))){
//									g2.fillArc((int)(s1.getXPos()-t/2), (int)(s1.getYPos()-t/2),t, t, 0, 360);
//								}
//							}
						}
					}
					// Sliders
					g2.setStroke(new BasicStroke(t,BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
					g2.setColor(YAPconstant.C_SLIDER_COLOR);
					for (int i= 0; i<sizeSliders;i++){
						g2.drawLine((int)sliders.get(i).getStartPoint().getXPos(), (int)sliders.get(i).getStartPoint().getYPos(), (int)sliders.get(i).getEndPoint().getXPos(), (int)sliders.get(i).getEndPoint().getYPos());
					}
					// Rotate it back! to display 
					// - the amount of lives in bottom left corner
					// - the win/lose output
					// - pause 
					g2.rotate(-DataModul.gInfo.getCurrAngle(), size/2, size/2);
					g2.setColor(YAPconstant.C_STARTCOUNTER_COLOR);
					g2.setFont(new Font("dialog", Font.BOLD, 64));
					g2.drawString(DataModul.gInfo.getCurrentLives()+"", 0, size-18);
					g2.setFont(new Font("dialog", Font.BOLD, 48));
					if (gi.isEndingGame()){
						if (gi.getWinnerName().equalsIgnoreCase(DataModul.clPlayer.getName())){
							g2.setColor(YAPconstant.C_WINNER_COLOR);
							g2.drawString("You Won!", size/3, size/3);
						}
						else{
							g2.setColor(YAPconstant.C_LOSER_COLOR);
							g2.drawString("You Lost!", size/4, size/4);
							g2.drawString("Play Again", size/4, size/2);
							g2.drawString(gi.getWinnerName() + " Won", size/4, 3*size/4);
						}
					}
					if (gi.isPaused()){
						g2.setColor(YAPconstant.C_PAUSE_COLOR);
						g2.drawString("game paused", size/4, size/2);
					}
					break;
				default:
					break;
				}
				// Set Mouse Cursor, not in switch to make sure the cursor wont stay as a Hand when the game starts at a bad moment
				if (getCurrentMousePos() != null){
					boolean bInside = false;
					for (int i = 0; i < sizeSides; i++){
						if (sides.get(i).pWall.contains(getCurrentMousePos().getXPos(), getCurrentMousePos().getYPos())){
							bInside = true;
							break;
						}
					}
					if (bInside && isUseChangedCursor){
						setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
					}
					else{
						setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
					}
				}
				Toolkit.getDefaultToolkit().sync();
			}
			else{
				System.out.println("GameInformation not available");
			}
		}
		catch(Error e){
			if(YAPconstant.C_DEBUG){ e.printStackTrace(); }
		}
	}

	/**
	 * Used for pickSide, determines which side that has been picked and sends the command to the server.
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseClicked(MouseEvent arg0) {
		if (DataModul.gInfo.getGameMode() == GameMode.lobby){
			if (DataModul.gInfo != null){
				ArrayList<Side> sides = DataModul.gInfo.getDrawSides();
				int sizeSides = sides.size();
				for (int i = 0; i < sizeSides; i++){
					if(sides.get(i).pWall.contains((arg0.getX()-YAPconstant.C_LINETHICKNESS)/dResized, (arg0.getY()-YAPconstant.C_LINETHICKNESS)/dResized)){
						DataModul.parseThread.writeMessage(Command.pickSide + YAPconstant.C_SEPARATOR+i);
						return;
					}
				}
			}
		}
	}
	/**
	 * Used to perform the hover-effect in lobby mode
	 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseMoved(MouseEvent arg0) {
		if (DataModul.gInfo.getGameMode() == GameMode.lobby){
			setCurrentMousePos(new Point((arg0.getX()-YAPconstant.C_LINETHICKNESS)/dResized, (arg0.getY()-YAPconstant.C_LINETHICKNESS)/dResized));
			DataModul.repaintFrames();
		}
	}

	/**
	 * @return Current mouse position
	 */
	public Point getCurrentMousePos() {
		return m_CurrentMousePos;
	}

	/**
	 * @param m_CurrentMousePos Current mouse position
	 */
	public void setCurrentMousePos(Point m_CurrentMousePos) {
		this.m_CurrentMousePos = m_CurrentMousePos;
	}
	
	/**
	 * Rotates the Playground in timeInMS around angleToRotate <br>
	 * Positive angle: counterclockwise<br>
	 * Negative angle: clockwise<br>
	 * @param timeInMS Time needed to rotate around angleToRotate
	 * @param angleToRotate Angle which is being rotated
	 */
	public void setAutomaticRotation(int timeInMS, double angleToRotate){
		if (tim == null){
			tim = new Timer((int)(1000/YAPconstant.C_FPS_LIMIT), this);
			tim.setRepeats(true);
			systemMillis = System.currentTimeMillis();
			rotTime = timeInMS;
			rotAngle = angleToRotate;
			//System.out.println("Rotation started. rotTime: " + rotTime + " rotAngle: " + rotAngle);
			tim.start();
		}
	}

	/**
	 * Resets the timer for automaticRotation
	 */
	public void resetTimer(){
		if (tim != null)
		{
			tim.stop();
			tim = null;
		}
	}
	/**
	 * Stops the timer for automaticRotation
	 */
	public void stopTimer(){
		if (tim != null)
		{
			tim.stop();
		}
	}
	
	/**
	 * Used to catch timer event and change the currentAngle
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	@Override
	public void actionPerformed(ActionEvent arg0) {
		if (arg0.getSource().equals(tim)){
			if (!DataModul.gInfo.isEndingGame()){
				long currMS = System.currentTimeMillis();
				if (!DataModul.gInfo.isPaused()){
					DataModul.gInfo.setCurrAngle(DataModul.gInfo.getCurrAngle()-(currMS-systemMillis)/(double)rotTime*rotAngle);
					if (DataModul.gInfo.getPlayerAngle()-rotAngle > DataModul.gInfo.getCurrAngle()){
						DataModul.gInfo.setCurrAngle(DataModul.gInfo.getPlayerAngle());
						tim.stop();
						tim = null;
					}
				}
				systemMillis = currMS;
				
			}
			else{
				tim.stop();
			}
		}
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {}
	@Override
	public void mouseExited(MouseEvent arg0) {}
	@Override
	public void mousePressed(MouseEvent arg0) {}
	@Override
	public void mouseReleased(MouseEvent arg0) {}
	@Override
	public void mouseDragged(MouseEvent arg0) {}
	@Override
	public void componentHidden(ComponentEvent arg0) {}
	@Override
	public void componentMoved(ComponentEvent arg0) {}
	@Override
	public void componentShown(ComponentEvent arg0) {}


}
